﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using BEPUphysics;
using BEPUphysics.Materials;
using BEPUphysics.Entities.Prefabs;

namespace ChimneySim
{
    public class ParticleEmitter
    {
        private Model model;
        public Model Model
        {
            get { return model; }
            set { model = value; }
        }

        private Vector3 position;
        public Vector3 Position
        {
            get { return position; }
            set { position = value; }
        }        

        private Vector3 velocity;

        private int maxParticles;

        public int MaxParticles
        {
            get { return maxParticles; }
            set { maxParticles = value; }
        }

        //num of new particles on 1 sec
        private float emmisionRate = 1;
        public float EmmisionRate
        {
            get { return emmisionRate; }
            set { emmisionRate = value; }
        }

        private float randPosMaxVal = 1;
        public float RandPosMaxVal
        {
            get { return randPosMaxVal; }
            set { randPosMaxVal = value; }
        }

        private float randVelMaxVal = 1;
        public float RandVelMaxVal
        {
            get { return randVelMaxVal; }
            set { randVelMaxVal = value; }
        }

        private TimeSpan lifeTime = new TimeSpan(0, 0, 10);
        public TimeSpan LifeTime
        {
            get { return lifeTime; }
            set { lifeTime = value; }
        }

        private List<Vector3> pPositions;
        public List<Vector3> PPositions
        {
            get { return pPositions; }
            set { pPositions = value; }
        }

        private List<Vector3> pVelocities;
        public List<Vector3> PVelocities
        {
            get { return pVelocities; }
            set { pVelocities = value; }
        }

        private List<int> pIndexes;
        public List<int> PIndexes
        {
            get { return pIndexes; }
            set { pIndexes = value; }
        }

        private bool[] allocatedIndexes;
        private TimeSpan[] pLifeTimes;
        private int numParticles;

        private Random rand;

        //Rigid particles
        private Space space;
        
        private List<Sphere> particles;        
        private Material material;
        private float mass = 0.5f;
        private float size = 0.05f;
        public float Size
        {
            get { return size; }
            set { size = value; }
        }

        public ParticleEmitter(Space space, Vector3 pos, Vector3 vel, int maxParticles)
        {
            position = pos;
            velocity = vel;
            this.maxParticles = maxParticles;
            rand = new Random();
            this.space = space;
            
            pPositions = new List<Vector3>();
            pVelocities = new List<Vector3>();
            pIndexes = new List<int>();
            pLifeTimes = new TimeSpan[maxParticles];
            allocatedIndexes = new bool[maxParticles];
            numParticles = 0;

            for (int i = 0; i < maxParticles; i++)
            {
                //pIndexes[i] = i;
                allocatedIndexes[i] = false;
                pLifeTimes[i] = lifeTime;
            }           

            //Rigid particles     
            particles = new List<Sphere>();
            material = new Material(0, 0, 1);

        }

        #region Creation

        public void SpawnRigidParticle()
        {
            Sphere particle;

            // Compute the location in a grid of particles + move the grid back so its center is at 0, 0, 0
            float x = (((float)rand.NextDouble() * 2) - 1) * randPosMaxVal;
            float y = 0;//(((float)rand.NextDouble() * 2) - 1) * randPosMaxVal;
            float z = (((float)rand.NextDouble() * 2) - 1) * randPosMaxVal;

            particle = new Sphere((position + new Vector3(x, y, z)), size);
            particle.Mass = mass;
            particle.Material = material;

            x = (((float)rand.NextDouble() * 2) - 1) * randVelMaxVal;
            y = (((float)rand.NextDouble() * 2) - 1) * randVelMaxVal;
            z = (((float)rand.NextDouble() * 2) - 1) * randVelMaxVal;            

            particle.LinearVelocity = velocity + new Vector3(x, y, z);

            particles.Add(particle);

            space.Add(particle);

            numParticles++;
        }

        private int FindProperIndex()
        {
            for (int i = 0; i < maxParticles; i++)
            {
                if (allocatedIndexes[i] == false)
                    return i;
            }
            return -1;
        }
        
        #endregion

        #region Update

        private TimeSpan time;

        public void Update(GameTime gameTime)
        {
            List<int> particlesToReset = new List<int>();
            pPositions.Clear();
            pVelocities.Clear();
            pIndexes.Clear();

            if(numParticles > 0)
            {
                pPositions.Clear();
                pVelocities.Clear();

                for(int i = 0; i < particles.Count; i++)
                {
                    Sphere body = particles[i];
                    Vector3 pos = body.Position;
                    pPositions.Add(pos);
                    pVelocities.Add(body.LinearVelocity);
                    pIndexes.Add(i);

                    pLifeTimes[i] -= gameTime.ElapsedGameTime;

                    if (pLifeTimes[i] <= TimeSpan.Zero)
                    {
                        particlesToReset.Add((int)i);
                    }
                }                
            }

            if (particlesToReset.Count > 0)
            {
                for (int i = 0; i < particlesToReset.Count; i++)
                {
                    // Compute the location in a grid of particles + move the grid back so its center is at 0, 0, 0
                    float x = (((float)rand.NextDouble() * 2) - 1) * randPosMaxVal;
                    float y = 0;// (((float)rand.NextDouble() * 2) - 1) * randPosMaxVal;
                    float z = (((float)rand.NextDouble() * 2) - 1) * randPosMaxVal;

                    Sphere body = particles[particlesToReset[i]];

                    body.Position = position + new Vector3(x, y, z);
                    pPositions[particlesToReset[i]] = position + new Vector3(x, y, z);

                    x = (((float)rand.NextDouble() * 2) - 1) * randVelMaxVal;
                    y = (((float)rand.NextDouble() * 2) - 1) * randVelMaxVal;
                    z = (((float)rand.NextDouble() * 2) - 1) * randVelMaxVal;

                    body.LinearVelocity = velocity + new Vector3(x, y, z);
                    pVelocities[particlesToReset[i]] = velocity + new Vector3(x, y, z);

                    pLifeTimes[particlesToReset[i]] = lifeTime;
                }
            }


            time += gameTime.ElapsedGameTime;

            float p = emmisionRate * (float)time.TotalSeconds;
            int numParticlesToSpawn = (int)p;

            if (numParticlesToSpawn > 0)
            {
                time = TimeSpan.Zero;//-= numParticlesToSpawn / emmisionRate;

                for (int i = 0; i < numParticlesToSpawn && numParticles < maxParticles; i++)
                {
                    SpawnRigidParticle();
                }
            }

            /*foreach (Px.RigidBody pr in particles)
            {
                pr.AddForce(-scene.Gravity, Px.ForceMode.Acceleration, true);
            }*/
            
            
        }
        
        
        public void AddForces(List<Vector3> forces, List<int> indexes)
        {
            for (int i = 0; i < indexes.Count; i++)
            {
                int index = indexes[i];

                particles[index].ApplyImpulse(Vector3.Zero, forces[i]);
                //particles[index].AddForce(forces[i], Px.ForceMode.Force, true);
            }
        }

        public void AddLinearVelocity(List<Vector3> velocities, List<int> indexes)
        {
            for (int i = 0; i < indexes.Count; i++)
            {
                int index = indexes[i];
                                
                particles[index].LinearVelocity += velocities[i];
            }
        }
        
        #endregion
    }
}
